#include <httpd.h>
#include <http_connection.h>
#include <http_protocol.h>
#include <http_config.h>
#include <ctype.h>

#define ONE_SECOND 1000000

module AP_MODULE_DECLARE_DATA stream_module;

typedef struct {
    int maxtries; /* how many times we should attempt to read more from the file */
    int timeout; /* how many seconds we should wait before another attempt */
} svr_cfg;

typedef enum { cmd_maxtries, cmd_timeout } cmd_parts;

static void *stream_cfg(apr_pool_t *p, server_rec *x)
{
    svr_cfg *new = (svr_cfg*)apr_palloc(p, sizeof(svr_cfg));

    new->maxtries = 2;
    new->timeout = 1;

    return (void *)new;
}

static int is_numeric(const char *val)
{
    const char *p;

    for (p = val; *p; ++p) {
        if (!isdigit(*p)) {
            return 0;
        }
    }
    return 1;
}

#define ASSERT_INT(val) \
    if (!is_numeric(val)) \
        return "Argument must be numeric"

static const char *set_param(cmd_parms *parms, void *cfg, const char *val)
{
    svr_cfg *m = ap_get_module_config(parms->server->module_config, &stream_module);

    switch ((cmd_parts)cfg) {
        case cmd_maxtries: ASSERT_INT(val); m->maxtries = atoi(val); break;
        case cmd_timeout: ASSERT_INT(val); m->timeout = atoi(val); break;
    }

    return NULL;
}

static void *stream_merge_cfg(apr_pool_t *pool, void *BASE, void *ADD)
{
    svr_cfg *base = (svr_cfg *)BASE;
    svr_cfg *add = (svr_cfg *)ADD;
    svr_cfg *m = (svr_cfg *)apr_palloc(pool, sizeof(svr_cfg));

    m->maxtries = add->maxtries;
    m->timeout = add->timeout;

    return (void *)m;
}

static int stream_handler(request_rec *r)
{
    if (!r->handler || strcmp(r->handler,"stream")) {
        return DECLINED;
    }
    if (M_GET != r->method_number) {
        return HTTP_METHOD_NOT_ALLOWED;
    }

    if (NULL == r->filename) {
        return HTTP_INTERNAL_SERVER_ERROR;
    }

    svr_cfg *conf = ap_get_module_config(r->server->module_config, &stream_module);

    apr_file_t *fd;
    if (APR_SUCCESS != apr_file_open(&fd, r->filename, APR_READ|APR_SHARELOCK|APR_SENDFILE_ENABLED, APR_OS_DEFAULT, r->pool)) {
        return HTTP_NOT_FOUND;
    }

    apr_finfo_t finfo;
    apr_size_t nbytes;

    /* send file contents once */
    if (APR_SUCCESS != ap_send_fd(fd, r, 0, r->finfo.size, &nbytes)) {
        return HTTP_INTERNAL_SERVER_ERROR;
    }

    apr_off_t fpos = nbytes;
    int tries = 0;

    while (tries < conf->maxtries) {
        /* flush output first */
        ap_flush_conn(r->connection);
        /* wait for file to change */
        apr_sleep(conf->timeout * ONE_SECOND);
        /* update file information */
        if (APR_SUCCESS != apr_file_info_get(&finfo,APR_FINFO_SIZE,fd)) {
            return HTTP_INTERNAL_SERVER_ERROR;
        }
        if (fpos < finfo.size) {
            /* file grew by finfo.size - fpos */
            if (APR_SUCCESS != ap_send_fd(fd, r, fpos, finfo.size - fpos, &nbytes)) {
                return HTTP_INTERNAL_SERVER_ERROR;
            }
            fpos += nbytes;
            /* we can try again */
            tries = 0;
        } else {
            /* file didn't grow */
            ++tries;
        }
    }
    apr_file_close(fd);

    return OK;
}

command_rec stream_cmds[] = {
    AP_INIT_TAKE1("StreamMaxTries", set_param, (void *)cmd_maxtries, OR_FILEINFO, "Number of times to try sending files"),
    AP_INIT_TAKE1("StreamTimeOut", set_param, (void *)cmd_timeout, OR_FILEINFO, "Time to wait before each attempt"),
    { NULL }
};

static void stream_hooks(apr_pool_t *pool)
{
    ap_hook_handler(stream_handler, NULL, NULL, APR_HOOK_MIDDLE);
}

module AP_MODULE_DECLARE_DATA stream_module = {
    STANDARD20_MODULE_STUFF,
    NULL,
    NULL,
    stream_cfg,
    stream_merge_cfg,
    stream_cmds,
    stream_hooks
};
